package jaoso.framework.security.impl;

import jaoso.framework.domain.Account;

import jaoso.framework.exception.BusinessException;

import jaoso.framework.service.SecurityService;
import jaoso.framework.service.ServiceLocator;

import java.io.IOException;

import java.util.Map;
import java.util.Properties;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

/**
 * A dependent Database login module for JAAS To build a generic one,just
 * replace the db operation with a generic way like use class loader to load the
 * driver and create the connection
 * 
 * @author Charles Huang
 * @since JDK1.4
 * @version $Id: DataBaseLoginModule.java,v 1.1.1.1 2004/02/04 03:52:13 mustang
 *          Exp $
 */
public class DataBaseLoginModule implements LoginModule
{
    /** DOCUMENT ME! */
    private CallbackHandler callbackHandler;

    // All the properties used to connec to DB

    /** DOCUMENT ME! */
    private Properties options;

    /** DOCUMENT ME! */
    private SecurityService securityService = (SecurityService) ServiceLocator
            .getInstance().getService("securityService");

    /** DOCUMENT ME! */
    private String username;

    //obtained from LoginContext

    /** DOCUMENT ME! */
    private Subject subject;

    /** DOCUMENT ME! */
    private boolean isAuthenticated = false;

    /**
     * Method to abort the authentication process (phase 2).
     * 
     * <p>
     * This method is called if the LoginContext's overall authentication
     * failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
     * LoginModules did not succeed).
     * </p>
     * 
     * <p>
     * If this LoginModule's own authentication attempt succeeded (checked by
     * retrieving the private state saved by the <code>login</code> method),
     * then this method cleans up any state that was originally saved.
     * </p>
     * 
     * <p>
     * </p>
     * 
     * @return true if this method succeeded, or false if this
     *         <code>LoginModule</code> should be ignored.
     * 
     * @exception LoginException
     *                if the abort fails
     */
    public final boolean abort() throws LoginException
    {
        return false;
    }

    /**
     * Method to commit the authentication process (phase 2).
     * 
     * <p>
     * This method is called if the LoginContext's overall authentication
     * succeeded (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
     * LoginModules succeeded).
     * </p>
     * 
     * <p>
     * If this LoginModule's own authentication attempt succeeded (checked by
     * retrieving the private state saved by the <code>login</code> method),
     * then this method associates relevant Principals and Credentials with the
     * <code>Subject</code> located in the <code>LoginModule</code>. If
     * this LoginModule's own authentication attempted failed, then this method
     * removes/destroys any state that was originally saved.
     * </p>
     * 
     * <p>
     * </p>
     * 
     * @return true if this method succeeded, or false if this
     *         <code>LoginModule</code> should be ignored.
     * 
     * @exception LoginException
     *                if the commit fails
     */
    public final boolean commit() throws LoginException
    {
        if (isAuthenticated)
        {
            subject.getPrincipals();

            //.add(new Account(username, null));
        } else
        {
            throw new LoginException("Authentication fails");
        }

        return isAuthenticated;
    }

    /**
     * Initialize this LoginModule.
     * 
     * <p>
     * This method is called by the <code>LoginContext</code> after this
     * <code>LoginModule</code> has been instantiated. The purpose of this
     * method is to initialize this <code>LoginModule</code> with the relevant
     * information. If this <code>LoginModule</code> does not understand any
     * of the data stored in <code>sharedState</code> or <code>options</code>
     * parameters, they can be ignored.
     * </p>
     * 
     * <p>
     * </p>
     * 
     * @param arg0
     *            the <code>Subject</code> to be authenticated.
     *            <p>
     * @param arg1
     *            a <code>CallbackHandler</code> for communicating with the
     *            end user (prompting for usernames and passwords, for example).
     *            <p>
     * @param arg2
     *            state shared with other configured LoginModules.
     *            <p>
     * @param arg3
     *            options specified in the login <code>Configuration</code>
     *            for this particular <code>LoginModule</code>.
     */
    public final void initialize(final Subject arg0,
            final CallbackHandler arg1, final Map arg2, final Map arg3)
    {
        this.subject = arg0;
        this.callbackHandler = arg1;
    }

    /**
     * Method to authenticate a <code>Subject</code> (phase 1).
     * 
     * <p>
     * The implementation of this method authenticates a <code>Subject</code>.
     * For example, it may prompt for <code>Subject</code> information such as
     * a username and password and then attempt to verify the password. This
     * method saves the result of the authentication attempt as private state
     * within the LoginModule.
     * </p>
     * 
     * <p>
     * </p>
     * 
     * @return true if the authentication succeeded, or false if this
     *         <code>LoginModule</code> should be ignored.
     * 
     * @exception LoginException
     *                if the authentication fails
     */
    public final boolean login() throws LoginException
    {
        try
        {
            // Retrieve the user name and password from the screen through the
            // callback handler
            final Callback[] calls = new Callback[2];
            calls[0] = new NameCallback("name");
            calls[1] = new PasswordCallback("Password", false);

            if (callbackHandler == null)
            {
                throw new LoginException("callback is null");
            }

            callbackHandler.handle(calls);
            username = ((NameCallback) calls[0]).getName();

            if (username.equals(null))
            {
                throw new LoginException("name must not be null");
            }

            final String password = String
                    .valueOf(((PasswordCallback) (calls[1])).getPassword());

            if (password.equals(null))
            {
                throw new LoginException("password must not be null");
            }

            // Find the user and match the password
            final Account account = securityService.findAccountByName(username);

            if (account == null)
            {
                throw new LoginException("user not found :" + username);
            }

            if (!password.equals(account.getPassword()))
            {
                throw new LoginException("Invalid password");
            }
        } catch (final BusinessException sqlExeption)
        {
            throw new LoginException(sqlExeption.getMessage());
        } catch (final IOException ioException)
        {
            throw new LoginException(ioException.getMessage());
        } catch (final UnsupportedCallbackException unsupported)
        {
            throw new LoginException(unsupported.getMessage());
        }

        isAuthenticated = true;

        // Nothnig went wrong,authenctication succeed
        return isAuthenticated;
    }

    /**
     * Method which logs out a <code>Subject</code>.
     * 
     * <p>
     * An implementation of this method might remove/destroy a Subject's
     * Principals and Credentials.
     * </p>
     * 
     * <p>
     * </p>
     * 
     * @return true if this method succeeded, or false if this
     *         <code>LoginModule</code> should be ignored.
     * 
     * @exception LoginException
     *                if the logout fails
     */
    public final boolean logout() throws LoginException
    {
        return false;
    }
}